#include <buola/scene/geodes/ccylinder.h>
#include <buola/scene/cmesh.h>
#include <buola/scene/cvertexdata.h>
#include <buola/scene/cindexdata.h>
#include <buola/scene/cmeshbuilder.h>
#include <BulletCollision/CollisionShapes/btCylinderShape.h>

namespace buola { namespace scene {

static PMesh cylinder_mesh(float pHeight,float pRadius,int pSegments)
{
    PMesh lMesh=new CMesh;
    
    CVertexData &lVertexData=lMesh->VertexData();
    
    lVertexData.AddField<float>(FIELD_POSITION,3);
    lVertexData.AddField<float>(FIELD_NORMAL,3);
    lVertexData.AddField<float>(FIELD_TEXCOORD,2);

    lVertexData.Resize(2*(pSegments+1));

    struct ATTRIBUTE_PACKED LVertex
    {
        float x,y,z;
        float nx,ny,nz;
        float u,v;
    };
    
    LVertex *lVertices=lVertexData.Map<LVertex>();

    CIndexData &lIndexData=lMesh->IndexData();
    
    lIndexData.Resize(6*pSegments);
    
    uint32_t *lIndices=lIndexData.Map();
    
    float lDeltaSegAngle=2*M_PI/pSegments;
    
    int lVertexIndex=0;
    
    for(int lSeg=0;lSeg<=pSegments;lSeg++)
    {
        float lX=pRadius*sin(lSeg*lDeltaSegAngle);
        float lZ=pRadius*cos(lSeg*lDeltaSegAngle);
        
        mat::CVec3f lNormal=normalize(mat::CVec3f(lX,0,lZ));

        lVertices->x=lX;
        lVertices->y=pHeight/2;
        lVertices->z=lZ;
        
        lVertices->nx=lNormal.x();
        lVertices->ny=lNormal.y();
        lVertices->nz=lNormal.z();

        lVertices++;
        
        lVertices->x=lX;
        lVertices->y=-pHeight/2;
        lVertices->z=lZ;
        
        lVertices->nx=lNormal.x();
        lVertices->ny=lNormal.y();
        lVertices->nz=lNormal.z();
        
        lVertices++;
        
        if(lSeg!=pSegments)
        {
            *lIndices++=lVertexIndex;
            *lIndices++=lVertexIndex+2;
            *lIndices++=lVertexIndex+1;
            *lIndices++=lVertexIndex+1;
            *lIndices++=lVertexIndex+2;
            *lIndices++=lVertexIndex+3;
        }

        lVertexIndex+=2;
    }
    
    lVertexData.Unmap();
    lIndexData.Unmap();
    
    lMesh->AddSubMesh("cylinder",EPrimitive::TRIANGLES,0,6*pSegments);

    return lMesh;
}
  
static PMesh cylinder_mesh_z(float pHeight,float pRadius,int pSegments)
{
    PMesh lMesh=new CMesh;
    
    CVertexData &lVertexData=lMesh->VertexData();
    
    lVertexData.AddField<float>(FIELD_POSITION,3);
    lVertexData.AddField<float>(FIELD_NORMAL,3);
    lVertexData.AddField<float>(FIELD_TEXCOORD,2);

    lVertexData.Resize(2*(pSegments+1));

    struct ATTRIBUTE_PACKED LVertex
    {
        float x,y,z;
        float nx,ny,nz;
        float u,v;
    };
    
    LVertex *lVertices=lVertexData.Map<LVertex>();

    CIndexData &lIndexData=lMesh->IndexData();
    
    lIndexData.Resize(6*pSegments);
    
    uint32_t *lIndices=lIndexData.Map();
    
    float lDeltaSegAngle=2*M_PI/pSegments;
    
    int lVertexIndex=0;
    
    for(int lSeg=0;lSeg<=pSegments;lSeg++)
    {
        float lX=pRadius*sin(lSeg*lDeltaSegAngle);
        float lZ=pRadius*cos(lSeg*lDeltaSegAngle);
        
        mat::CVec3f lNormal=normalize(mat::CVec3f(lX,0,lZ));

        lVertices->x=lX;
        lVertices->z=pHeight/2;
        lVertices->y=lZ;
        
        lVertices->nx=lNormal.x();
        lVertices->nz=lNormal.y();
        lVertices->ny=lNormal.z();

        lVertices++;
        
        lVertices->x=lX;
        lVertices->z=-pHeight/2;
        lVertices->y=lZ;
        
        lVertices->nx=lNormal.x();
        lVertices->nz=lNormal.y();
        lVertices->ny=lNormal.z();
        
        lVertices++;
        
        if(lSeg!=pSegments)
        {
            *lIndices++=lVertexIndex;
            *lIndices++=lVertexIndex+2;
            *lIndices++=lVertexIndex+1;
            *lIndices++=lVertexIndex+1;
            *lIndices++=lVertexIndex+2;
            *lIndices++=lVertexIndex+3;
        }

        lVertexIndex+=2;
    }
    
    lVertexData.Unmap();
    lIndexData.Unmap();
    
    lMesh->AddSubMesh("cylinder",EPrimitive::TRIANGLES,0,6*pSegments);

    return lMesh;
}
  
CCylinder::CCylinder(double pHeight,double pRadius,int pSegments,bool pZ)
    :   CGeode(pZ?cylinder_mesh_z(pHeight,pRadius,pSegments):cylinder_mesh(pHeight,pRadius,pSegments))
    ,   mHeight(pHeight)
    ,   mRadius(pRadius)
    ,   mZ(pZ)
{
}

CCylinder::~CCylinder()
{
}

PBulletCollisionShape CCylinder::GetCollisionShape()
{
    if(mZ)
        return PBulletCollisionShape(new btCylinderShapeZ(btVector3(mRadius,mRadius,mHeight/2)));
    else
        return PBulletCollisionShape(new btCylinderShape(btVector3(mRadius,mHeight/2,mRadius)));
}

/*namespace gl*/ } /*namespace buola*/ }
